//Wrapper for EE::Matrix
#pragma once
#include "stdafx.h"

namespace EsenthelNet
{	
	/// <summary>
	/// Matrix 4x3 (orientation + scale + position)
	/// </summary>
	public ref struct Matrix {
	internal:
		Matrix(EE::Matrix m){
			matrix = new EE::Matrix();
			*matrix = m;
		}
		Matrix(EE::Matrix* m){
			matrix = m;
		}
		EE::Matrix* matrix;
	public:
		//Components:
		Prop(float, XX, matrix->xx) Prop(float, XY, matrix->xy) Prop(float, XZ, matrix->xz)
		Prop(float, YX, matrix->yx) Prop(float, YY, matrix->yy) Prop(float, YZ, matrix->yz)
		Prop(float, ZX, matrix->zx) Prop(float, ZY, matrix->zy) Prop(float, ZZ, matrix->zz)
		Prop(float, _X, matrix->_x) Prop(float, _Y, matrix->_y) Prop(float, _Z, matrix->_z)

		///<summary>Right vector</summary>
		PropVecP(X, matrix->x);
		///<summary>Up vector</summary>
		PropVecP(Y, matrix->y);
		///<summary>Forward vector</summary>
		PropVecP(Z, matrix->z);
		///<summary>Position vector</summary>
		PropVecP(Position, matrix->pos);

		//Operators:
		static EsenthelNet::Matrix^ operator +(EsenthelNet::Matrix^ m, EsenthelNet::Vec^ v) { return gcnew EsenthelNet::Matrix(*m->matrix + *v->vec); }
		static EsenthelNet::Matrix^ operator -(EsenthelNet::Matrix^ m, EsenthelNet::Vec^ v) { return gcnew EsenthelNet::Matrix(*m->matrix - *v->vec); }
		static EsenthelNet::Matrix^ operator *(EsenthelNet::Matrix^ m1, EsenthelNet::Matrix^ m2) { return gcnew EsenthelNet::Matrix(*m1->matrix * *m2->matrix); }
		static EsenthelNet::Matrix^ operator /(EsenthelNet::Matrix^ m1, EsenthelNet::Matrix^ m2) { return gcnew EsenthelNet::Matrix(*m1->matrix / *m2->matrix); }
		static EsenthelNet::Matrix^ operator ~(EsenthelNet::Matrix^ m) { return gcnew EsenthelNet::Matrix(~*m->matrix); }

		///<summary>Move</summary>
		EsenthelNet::Matrix^ Move(float x, float y, float z) { *matrix += EE::Vec(x, y, z); return this;}
		///<summary>Move</summary>
		EsenthelNet::Matrix^ Move(EsenthelNet::Vec^ v) { *matrix += *v->vec; return this;}
		///<summary>Scale</summary>
		EsenthelNet::Matrix^ Scale(float scale) { *matrix *= scale; return this;}
		///<summary>Scale</summary>
		EsenthelNet::Matrix^ Scale(EsenthelNet::Vec^ s) { *matrix *= *s->vec; return this;}
		///<summary>Scale orientation only</summary>
		EsenthelNet::Matrix^ ScaleOrn(float scale) { matrix->orn *= scale; return this;}
		///<summary>Scale orientation only</summary>
		EsenthelNet::Matrix^ ScaleOrn(EsenthelNet::Vec^ s) { matrix->orn *= *s->vec; return this;}
		///<summary>Scale along 'dir' direction by 'scale' value, 'dir' must be normalized</summary>
		EsenthelNet::Matrix^ Scale(EsenthelNet::Vec^ dir, float scale) { matrix->scale(*dir->vec, scale); return this;}
		///<summary>Scale along plane of 'nrm' normal by 'scale' value, 'nrm' must be normalized</summary>
		EsenthelNet::Matrix^ ScalePlane(EsenthelNet::Vec^ nrm, float scale) { matrix->scalePlane(*nrm->vec, scale); return this;}

		///<summary>Normalize scale</summary>
		EsenthelNet::Matrix^ Normalize() { matrix->orn.normalize(); return this; }
		///<summary>Normalize scale to 'scale'</summary>
		EsenthelNet::Matrix^ Normalize(float scale) { matrix->orn.normalize(scale); return this; }

		///<summary>Rotate by x axis</summary>
		EsenthelNet::Matrix^ RotateX(float angle) { matrix->rotateX(angle); return this; }
		///<summary>Rotate by y axis</summary>
		EsenthelNet::Matrix^ RotateY(float angle) { matrix->rotateY(angle); return this; }
		///<summary>Rotate by z axis</summary>
		EsenthelNet::Matrix^ RotateZ(float angle) { matrix->rotateZ(angle); return this; }
		///<summary>Rotate by x axis and then by y axis, works the same as rotateX(x).rotateY(y) but faster</summary>
		EsenthelNet::Matrix^ RotateXY(float x, float y) { matrix->rotateXY(x, y); return this; }
		///<summary>Rotate by vector, 'axis' must be normalized</summary>
		EsenthelNet::Matrix^ Rotate(EsenthelNet::Vec^ axis, float angle) { matrix->rotate(*axis->vec, angle); return this; }

		///<summary>Set identity</summary>
		EsenthelNet::Matrix^ ToIdentity() { matrix->identity(); return this; }

		///<summary>Set positioned identity</summary>
		EsenthelNet::Matrix^ SetPos(EsenthelNet::Vec^ v) { matrix->setPos(*v->vec); return this; }
		///<summary>Set scaled identity</summary>
		EsenthelNet::Matrix^ SetScale(float scale) { matrix->setScale(scale); return this; }
		///<summary>Set scaled identity</summary>
		EsenthelNet::Matrix^ SetScale(EsenthelNet::Vec^ v) { matrix->setScale(*v->vec); return this; }
		///<summary>Set positioned and scaled identity</summary>
		EsenthelNet::Matrix^ SetPosScale(EsenthelNet::Vec^ v, float scale) { matrix->setPosScale(*v->vec, scale); return this; }
		///<summary>Set positioned and scaled identity</summary>
		EsenthelNet::Matrix^ SetPosScale(EsenthelNet::Vec^ v1, EsenthelNet::Vec^ v2) { matrix->setPosScale(*v1->vec, *v2->vec); return this; }

		/// <summary>Set x-rotated identity</summary>
		EsenthelNet::Matrix^ SetRotateX(float angle) { matrix->setRotateX(angle); return this; }
		/// <summary>Set y-rotated identity</summary>
		EsenthelNet::Matrix^ SetRotateY(float angle) { matrix->setRotateY(angle); return this; }
		/// <summary>Set z-rotated identity</summary>
		EsenthelNet::Matrix^ SetRotateZ(float angle) { matrix->setRotateZ(angle); return this; }
		/// <summary>Set x-y-rotated identity, works the same as setRotateX(x).rotateY(y) but faster</summary>
		EsenthelNet::Matrix^ SetRotateZY(float x, float y) { matrix->setRotateXY(x, y); return this; }
		/// <summary>Set rotated by vector identity, 'axis' must be normalized</summary>
		EsenthelNet::Matrix^ SetRotate(EsenthelNet::Vec^ axis, float angle) { matrix->setRotate(*axis->vec, angle); return this; }

		/// <summary>Set pos='pos', y='up' and calculate correct x,z, 'up' must be normalized</summary>
		EsenthelNet::Matrix^ SetPosUp(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ up) { matrix->setPosUp(*pos->vec, *up->vec); return this; }
		/// <summary>Set pos='pos', z='dir' and calculate correct x,y, 'dir' must be normalized</summary>
		EsenthelNet::Matrix^ SetPosDir(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir) { matrix->setPosDir(*pos->vec, *dir->vec); return this; }
		/// <summary>Set pos='pos', z='dir', y='up' and calculate correct x, 'dir up' must be normalized</summary>
		EsenthelNet::Matrix^ SetPosDir(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir, EsenthelNet::Vec^ up) { matrix->setPosDir(*pos->vec, *dir->vec, *up->vec); return this; }
		/// <summary>Set pos='pos', z='dir', y='up', x='right', 'dir up right' must be normalized</summary>
		EsenthelNet::Matrix^ SetPosDir(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir, EsenthelNet::Vec^ up, EsenthelNet::Vec^ right) { matrix->setPosDir(*pos->vec, *dir->vec, *up->vec, *right->vec); return this; }
		/// <summary>Calculate matrix which rotates 'dir_from' into 'dir_to', using blend value and additional roll angle, 'dir_from dir_to' must be normalized</summary>
		EsenthelNet::Matrix^ SetRotation(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir_from, EsenthelNet::Vec^ dir_to) { matrix->setRotation(*pos->vec, *dir_from->vec, *dir_to->vec); return this; }
		/// <summary>Calculate matrix which rotates 'dir_from' into 'dir_to', using blend value and additional roll angle, 'dir_from dir_to' must be normalized</summary>
		EsenthelNet::Matrix^ SetRotation(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir_from, EsenthelNet::Vec^ dir_to, float blend) { matrix->setRotation(*pos->vec, *dir_from->vec, *dir_to->vec, blend); return this; }
		/// <summary>Calculate matrix which rotates 'dir_from' into 'dir_to', using blend value and additional roll angle, 'dir_from dir_to' must be normalized</summary>
		EsenthelNet::Matrix^ SetRotation(EsenthelNet::Vec^ pos, EsenthelNet::Vec^ dir_from, EsenthelNet::Vec^ dir_to, float blend, float roll) { matrix->setRotation(*pos->vec, *dir_from->vec, *dir_to->vec, blend, roll); return this; }

		/// <summary>Get average axis scale</summary>
		float AvgScale() { return matrix->orn.avgScale(); }
		/// <summary>Get maximum  axis scale</summary>
		float MaxScale() { return matrix->orn.maxScale(); }
		/// <summary>Get rotation angles, this allows to reconstruct the matrix using "setRotateZ(angle.z).rotateX(angle.x).rotateY(angle.y)" or faster by using "setRotateZ(angle.z).rotateXY(angle.x,angle.y)"</summary>
		EsenthelNet::Vec^ Angles() { return gcnew EsenthelNet::Vec(matrix->orn.angle()); }
		/// <summary>Get rotation axis</summary>
		EsenthelNet::Vec^ Axis(bool normalized) { return gcnew EsenthelNet::Vec(matrix->orn.axis(normalized)); }
		/// <summary>Get rotation angle</summary>
		float Angle(bool normalized) { return matrix->orn.angle(normalized); }
		/// <summary>Get rotation axis and angle</summary>
		float AxisAngle(EsenthelNet::Vec^ axis, bool normalized) { return matrix->orn.axisAngle(*axis->vec, normalized); }

		/// <summary>Set transformation at position</summary>
		EsenthelNet::Matrix^ SetTransformAtPos(EsenthelNet::Vec^ pos, EsenthelNet::Matrix^ mat) { matrix->setPos(-*pos->vec).mul(*mat->matrix).move(*pos->vec); return this; }
		/// <summary>Transformation at position</summary>
		EsenthelNet::Matrix^ TransformAtPos(EsenthelNet::Vec^ pos, EsenthelNet::Matrix^ mat) { matrix->move(-*pos->vec).mul(*mat->matrix).move(*pos->vec); return this; }

		/// <summary>Draw</summary>
		void Draw() { matrix->orn.draw(matrix->pos); }
		/// <summary>Draw</summary>
		void Draw(EsenthelNet::Color^ x_color) { matrix->orn.draw(matrix->pos, *x_color->color); }
		/// <summary>Draw</summary>
		void Draw(EsenthelNet::Color^ x_color, EsenthelNet::Color^ y_color) { matrix->orn.draw(matrix->pos, *x_color->color, *y_color->color); }
		/// <summary>Draw</summary>
		void Draw(EsenthelNet::Color^ x_color, EsenthelNet::Color^ y_color, EsenthelNet::Color^ z_color) { matrix->orn.draw(matrix->pos, *x_color->color, *y_color->color, *z_color->color); }
		/// <summary>Draw</summary>
		void Draw(EsenthelNet::Color^ x_color, EsenthelNet::Color^ y_color, EsenthelNet::Color^ z_color, bool arrow) { matrix->orn.draw(matrix->pos, *x_color->color, *y_color->color, *z_color->color, arrow); }
		

		Matrix() {
			matrix = new EE::Matrix();
		}
		Matrix(float scale) {
			matrix = new EE::Matrix(scale);
		}
		Matrix(EsenthelNet::Vec^ pos) {
			matrix = new EE::Matrix(*pos->vec);
		}
		Matrix(EsenthelNet::Vec^ pos, float scale) {
			matrix = new EE::Matrix(*pos->vec, scale);
		}

		~Matrix() {
			delete matrix;
		}

		static property EsenthelNet::Matrix^ Identity {
			EsenthelNet::Matrix^ get() {
				EsenthelNet::Matrix^ m = gcnew EsenthelNet::Matrix(EE::MatrixIdentity);
				
				return m;
			}
		}

		/// <summary>
        /// set active object rendering matrix
        /// </summary>
		static void Set(EsenthelNet::Matrix^ mat) {
			EE::SetMatrix(*mat->matrix);
		}

	};
}